Implement caching among fingerprint resolutions
authorAlex Crichton <alex@alexcrichton.com>
Fri, 29 May 2015 18:22:44 +0000 (11:22 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 3 Jun 2015 01:05:47 +0000 (18:05 -0700)
This commit adds support for caching the resolved value of a fingerprint among
calls to `resolve`. Due to the recursive nature of a fingerprint, it means that
`resolve` is currently executed a very large number of times for packages which
at transitively dependend up on many times. By caching the returned value of a
fingerprint we're able to prevent extraneous calls into the filesystem or
extraneous hashing.

This also adds an `Arc` to share a `Fingerprint` among all its dependencies as
each fingerprint stores a list of fingerprints that it depends on, and if
they're not shared then the cache isn't exactly the most helpful!

For a noop `cargo build` on Servo (e.g. everything was already built), this
decreased Cargo's runtime from 5s to ~4.5s

src/cargo/ops/cargo_rustc/fingerprint.rs

index bbcb513ebccebf13c2ee905924099243fca6e5a6..8ca4800f26149a8d60b9fc1234679516446b5212 100644 (file)
@@ -2,6 +2,7 @@ use std::fs::{self, File, OpenOptions};
 use std::io::prelude::*;
 use std::io::{BufReader, SeekFrom};
 use std::path::{Path, PathBuf};
+use std::sync::{Arc, Mutex};
 
 use core::{Package, Target, Profile};
 use util::{self, MTime};
@@ -87,11 +88,12 @@ pub fn prepare_target<'a, 'cfg>(cx: &mut Context<'a, 'cfg>,
 /// `DependencyQueue`, but it also needs to be retained here because Cargo can
 /// be interrupted while executing, losing the state of the `DependencyQueue`
 /// graph.
-#[derive(Clone)]
-pub struct Fingerprint {
+pub type Fingerprint = Arc<FingerprintInner>;
+struct FingerprintInner {
     extra: String,
     deps: Vec<Fingerprint>,
     local: LocalFingerprint,
+    resolved: Mutex<Option<String>>,
 }
 
 #[derive(Clone)]
@@ -100,8 +102,13 @@ enum LocalFingerprint {
     MtimeBased(Option<MTime>, PathBuf),
 }
 
-impl Fingerprint {
+impl FingerprintInner {
     fn resolve(&self, force: bool) -> CargoResult<String> {
+        if !force {
+            if let Some(ref s) = *self.resolved.lock().unwrap() {
+                return Ok(s.clone())
+            }
+        }
         let mut deps: Vec<_> = try!(self.deps.iter().map(|s| {
             s.resolve(force)
         }).collect());
@@ -114,8 +121,10 @@ impl Fingerprint {
                 try!(MTime::of(p)).to_string()
             }
         };
-        debug!("inputs: {} {} {:?}", known, self.extra, deps);
-        Ok(util::short_hash(&(known, &self.extra, &deps)))
+        let resolved = util::short_hash(&(&known, &self.extra, &deps));
+        debug!("inputs: {} {} {:?} => {}", known, self.extra, deps, resolved);
+        *self.resolved.lock().unwrap() = Some(resolved.clone());
+        Ok(resolved)
     }
 }
 
@@ -188,11 +197,12 @@ fn calculate<'a, 'cfg>(cx: &mut Context<'a, 'cfg>,
     } else {
         LocalFingerprint::Precalculated(try!(calculate_pkg_fingerprint(cx, pkg)))
     };
-    let fingerprint = Fingerprint {
+    let fingerprint = Arc::new(FingerprintInner {
         extra: extra,
         deps: deps,
         local: local,
-    };
+        resolved: Mutex::new(None),
+    });
     cx.fingerprints.insert(key, fingerprint.clone());
     Ok(fingerprint)
 }
@@ -234,11 +244,12 @@ pub fn prepare_build_cmd(cx: &mut Context, pkg: &Package, kind: Kind)
     info!("fingerprint at: {}", loc.display());
 
     let new_fingerprint = try!(calculate_build_cmd_fingerprint(cx, pkg));
-    let new_fingerprint = Fingerprint {
+    let new_fingerprint = Arc::new(FingerprintInner {
         extra: String::new(),
         deps: Vec::new(),
         local: LocalFingerprint::Precalculated(new_fingerprint),
-    };
+        resolved: Mutex::new(None),
+    });
 
     let is_fresh = try!(is_fresh(&loc, &new_fingerprint));
 
@@ -269,7 +280,9 @@ pub fn prepare_init(cx: &mut Context, pkg: &Package, kind: Kind)
 
 /// Given the data to build and write a fingerprint, generate some Work
 /// instances to actually perform the necessary work.
-fn prepare(is_fresh: bool, loc: PathBuf, fingerprint: Fingerprint) -> Preparation {
+fn prepare(is_fresh: bool,
+           loc: PathBuf,
+           fingerprint: Fingerprint) -> Preparation {
     let write_fingerprint = Work::new(move |_| {
         debug!("write fingerprint: {}", loc.display());
         let fingerprint = try!(fingerprint.resolve(true).chain_error(|| {